package auth

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	"io/ioutil"
	"net/http"
	"time"

	"github.com/astaxie/beego/orm"
	"github.com/golang/glog"

	"eccgateway/pkg/api"
	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/config"
	"eccgateway/pkg/db/auth"
	"eccgateway/pkg/util"
)

var opServiceToken string
var tokenExpireTime time.Time
var iManagerAddress string

const defaultExpireTime = 24 * 60

func ValidUserPasswordAndGetRoles(name, password string) (bool, *auth.User, error) {
	o := orm.NewOrm()
	user, err := auth.GetUserByName(o, name)
	if err == orm.ErrNoRows {
		return false, nil, nil
	}
	if err != nil {
		return false, nil, err
	}
	user.Password, err = util.Decrypt(user.Password)
	if err != nil {
		return false, nil, fmt.Errorf("decrypt failed, %v", err)
	}
	if user.Password != password {
		return false, nil, nil
	}

	return true, &user, nil
}

func GetUserByUUID(uuid string) (*auth.User, error) {
	o := orm.NewOrm()
	user, err := auth.GetUserByUUID(o, uuid)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func GetUserSecret(name string) (*authapi.UserSecret, error) {
	var err error
	if len(opServiceToken) == 0 || tokenExpireTime.Before(time.Now()) {
		err = InitOpServiceToken()
		if err != nil {
			glog.Errorf("init op_service token failed, err: %v", err)
			return nil, err
		}
	}
	if len(iManagerAddress) == 0 {
		iManagerAddress = strings.Trim(config.GetConfig().String("IManagerAddress"), "/")
	}

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v/v1/auth/user/%v/secret", iManagerAddress, name), nil)
	if err != nil {
		glog.Errorf("create request for get user[%v]'s secret token failed, err: %v", name, err)
		return nil, err
	}
	req.Header.Set(authapi.TokenHeaderKey, opServiceToken)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		glog.Errorf("do request for get user[%v]'s secret token failed, err: %v", name, err)
		return nil, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("do request for get user[%v]'s secret token failed, err: %v", name, err)
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		errorMessage, err := resolveErrorMessage(body)
		if err != nil {
			glog.Errorf("do request for get user[%v]'s secret token failed, and resolve error message failed, err: %v", name, err)
			return nil, err
		}
		return nil, fmt.Errorf("%v", errorMessage)
	}

	res := authapi.UserSecret{}
	err = json.Unmarshal(body, &res)
	if err != nil {
		glog.Errorf("unmarshal user[%v]'s secret failed, err: %v", name, err)
		return nil, err
	}
	return &res, nil
}

func InitOpServiceToken() error {
	startTime := time.Now()
	harborUser := config.GetConfig().String("HarborUser")
	harborPassword := config.GetConfig().String("HarborPassword")
	if len(iManagerAddress) == 0 {
		iManagerAddress = strings.Trim(config.GetConfig().String("IManagerAddress"), "/")
		glog.Infof("iManagerAddress: %v", iManagerAddress)
	}

	reqToken := authapi.ReqToken{
		Auth: authapi.ReqTokenAuth{
			Name:     harborUser,
			Password: harborPassword,
		},
		Scope: authapi.ReqTokenScope{Duration: defaultExpireTime},
	}
	reqBody, _ := json.Marshal(reqToken)

	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%v/v1/auth/tokens", iManagerAddress), bytes.NewBuffer(reqBody))
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		errorMessage, err := resolveErrorMessage(body)
		if err != nil {
			return err
		}
		return fmt.Errorf("%v", errorMessage)
	}
	glog.Infof("op_service token refresh successful")

	opServiceToken = resp.Header.Get(authapi.TokenHeaderKey)
	res := authapi.RespToken{}
	err = json.Unmarshal(body, &res)
	if err != nil || res.ExpiresAt.Before(tokenExpireTime) {
		glog.Warningf("unmarshal op_service token response failed, use request time, err: %v", err)
		tokenExpireTime = startTime.Add(defaultExpireTime * time.Minute)
		return nil
	}
	tokenExpireTime = res.ExpiresAt
	return nil
}

func resolveErrorMessage(body []byte) (string, error) {
	errorMessage := api.ErrorResponse{}
	err := json.Unmarshal(body, &errorMessage)
	if err != nil {
		return "", err
	}
	return errorMessage.ErrorMessage, nil
}
